/*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* "The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations under
* the License.
*
* The Original Code is ICEfaces 1.5 open source software code, released
* November 5, 2006. The Initial Developer of the Original Code is ICEsoft
* Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
* 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
*
* Contributor(s): _____________________.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
* License), in which case the provisions of the LGPL License are
* applicable instead of those above. If you wish to allow use of your
* version of this file only under the terms of the LGPL License and not to
* allow others to use your version of this file under the MPL, indicate
* your decision by deleting the provisions above and replace them with
* the notice and other provisions required by the LGPL License. If you do
* not delete the provisions above, a recipient may use your version of
* this file under either the MPL or the LGPL License."
*
*/
package co.edu.usbcali.utilities;

import javax.faces.model.DataModel;


/**
 * A special type of JSF DataModel to allow a datatable and datapaginator
 * to page through a large set of data without having to hold the entire
 * set of data in memory at once.
 * Any time a managed bean wants to avoid holding an entire dataset,
 * the managed bean should declare an inner class which extends this
 * class and implements the fetchData method. This method is called
 * as needed when the table requires data that isn't available in the
 * current data page held by this object.
 * This does require the managed bean (and in general the business
 * method that the managed bean uses) to provide the data wrapped in
 * a DataPage object that provides info on the full size of the dataset.
 *
 * @author Zathura Code Generator http://code.google.com/p/zathura
 *
 */
public abstract class PagedListDataModel<T> extends DataModel {
    int pageSize;
    int rowIndex;
    DataPage<T> page;

    // Triggers a fetch from the database
    protected boolean dirtyData = false;

    /*
    * Create a datamodel that pages through the data showing the specified
    * number of rows on each page.
    */
    public PagedListDataModel(int pageSize) {
        super();
        this.pageSize = pageSize;
        this.rowIndex = -1;
        this.page = null;
    }

    /**
    * Not used in this class; data is fetched via a callback to the
    * fetchData method rather than by explicitly assigning a list.
    */
    @Override
    public void setWrappedData(Object o) {
        throw new UnsupportedOperationException("setWrappedData");
    }

    @Override
    public int getRowIndex() {
        return rowIndex;
    }

    /**
    * Specify what the "current row" within the dataset is. Note that
    * the UIData component will repeatedly call this method followed
    * by getRowData to obtain the objects to render in the table.
    */
    @Override
    public void setRowIndex(int index) {
        rowIndex = index;
    }

    /**
    * Return the total number of rows of data available (not just the
    * number of rows in the current page!).
    */
    @Override
    public int getRowCount() {
        return getPage().getDatasetSize();
    }

    /**
    * Return a DataPage object; if one is not currently available then
    * fetch one. Note that this doesn't ensure that the datapage
    * returned includes the current rowIndex row; see getRowData.
    */
    private DataPage<T> getPage() {
        if (page != null) {
            return page;
        }

        int rowIndex = getRowIndex();
        int startRow = rowIndex;

        if (rowIndex == -1) {
            // even when no row is selected, we still need a page
            // object so that we know the amount of data available.
            startRow = 0;
        }

        // invoke method on enclosing class
        page = fetchPage(startRow, pageSize);

        return page;
    }

    /**
    * Return the object corresponding to the current rowIndex.
    * If the DataPage object currently cached doesn't include that
    * index or the data is marked as dirty, then fetchPage is called
    * to retrieve the appropriate page.
    */
    @Override
    public Object getRowData() {
        if (rowIndex < 0) {
            throw new IllegalArgumentException(
                "Invalid rowIndex for PagedListDataModel; not within page");
        }

        // ensure page exists; if rowIndex is beyond dataset size, then 
        // we should still get back a DataPage object with the dataset size
        // in it...
        if (page == null) {
            page = fetchPage(rowIndex, pageSize);
        }

        // Check if rowIndex is equal to startRow,
        // useful for dynamic sorting on pages
        if ((rowIndex == page.getStartRow()) && dirtyData) {
            page = fetchPage(rowIndex, pageSize);
        }

        int datasetSize = page.getDatasetSize();
        int startRow = page.getStartRow();
        int nRows = page.getData().size();
        int endRow = startRow + nRows;

        if (rowIndex >= datasetSize) {
            throw new IllegalArgumentException("Invalid rowIndex");
        }

        if (rowIndex < startRow) {
            page = fetchPage(rowIndex, pageSize);
            startRow = page.getStartRow();
        } else if (rowIndex >= endRow) {
            page = fetchPage(rowIndex, pageSize);
            startRow = page.getStartRow();
        }

        return page.getData().get(rowIndex - startRow);
    }

    @Override
    public Object getWrappedData() {
        return page.getData();
    }

    /**
    * Return true if the rowIndex value is currently set to a
    * value that matches some element in the dataset. Note that
    * it may match a row that is not in the currently cached
    * DataPage; if so then when getRowData is called the
    * required DataPage will be fetched by calling fetchData.
    */
    @Override
    public boolean isRowAvailable() {
        DataPage<T> page = getPage();

        if (page == null) {
            return false;
        }

        int rowIndex = getRowIndex();

        if (rowIndex < 0) {
            return false;
        } else if (rowIndex >= page.getDatasetSize()) {
            return false;
        } else {
            return true;
        }
    }

    /**
    * Method which must be implemented in cooperation with the
    * managed bean class to fetch data on demand.
    */
    public abstract DataPage<T> fetchPage(int startRow, int pageSize);

    public boolean isDirtyData() {
        return dirtyData;
    }

    public void setDirtyData(boolean dirtyData) {
        this.dirtyData = dirtyData;
    }

    public void setDirtyData() {
        dirtyData = true;
    }
}
